using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class HoudiniCloudsRenderPass : ScriptableRenderPass
{
    #region shaderId声明

    private const string KRenderTag = "Houdini Volumetric Clouds Effects"; // 会出现在Debugger中
    private static readonly int SkyModeId = Shader.PropertyToID("_sky_mode");
    private static readonly int DebugModeId = Shader.PropertyToID("_debug_mode");

    private static readonly int DebugIterationId = Shader.PropertyToID("_debug_iteration");

    // private static readonly int DebugEnhancedSTId = Shader.PropertyToID("_enhanced_ST");
    private static readonly int CamFrustumId = Shader.PropertyToID("_CamFrustum");
    private static readonly int CamToWorldId = Shader.PropertyToID("_CamToWorld");
    private static readonly int AccuracyId = Shader.PropertyToID("_accuracy");
    private static readonly int IndexId = Shader.PropertyToID("_index");
    private static readonly int DataId = Shader.PropertyToID("_data");

    private static readonly int NoiseId = Shader.PropertyToID("_noise");

    // private static readonly int SinId = Shader.PropertyToID("_sin");
    private static readonly int CornerInId = Shader.PropertyToID("_corner_in");
    private static readonly int CornerOutId = Shader.PropertyToID("_corner_out");
    private static readonly int DataTexelId = Shader.PropertyToID("_data_texel");

    private static readonly int MovementId = Shader.PropertyToID("_movement");
    private static readonly int FbmId = Shader.PropertyToID("_fbm");
    private static readonly int ClipPanelId = Shader.PropertyToID("_clip_panel");
    private static readonly int CloudScaleId = Shader.PropertyToID("_cloud_scale");
    private static readonly int HighResScaleId = Shader.PropertyToID("_high_res_scale");

    private static readonly int ColorAdjustId = Shader.PropertyToID("_color_adjust");

    private static readonly int SkyColorId = Shader.PropertyToID("_sky_color");
    private static readonly int CloudColorId = Shader.PropertyToID("_cloud_color");
    private static readonly int ShadowParmId = Shader.PropertyToID("_shadow_parm");

    // 散射
    private static readonly int PhaseParamId = Shader.PropertyToID("_phase_params");

    #endregion

    #region Prepare

    private HoudiniCloudsVolume _cloudsVolume;
    private readonly Material _cloudMaterial;

    private RenderTargetIdentifier _source, temporaryBuffer;
    private int downSampleDepthId, downSampleOnceId, downSampleTwiceId, downSampleThirdId, shadowmapId, colorCloudId, finalColorId;

    #region ShadowMappingRelated

    // private readonly Camera _depthCamera;
    // private readonly Matrix4x4 _biasMatrix;
    // private static readonly int ShadowMapRTId = Shader.PropertyToID("_shadow_map");
    // private static readonly int ShadowCamVpId = Shader.PropertyToID("_shadow_cam_VP");
    // private static readonly int ShadowCamVId = Shader.PropertyToID("_shadow_cam_V");
    // private static readonly int LookupOffsetId = Shader.PropertyToID("_lookup_offset");

    #endregion


    // The constructor of the pass. Here you can set any material properties that do not need to be updated on a per-frame basis.
    public HoudiniCloudsRenderPass() // 构造函数
    {
        var shader = Shader.Find("MROP/houdini_cloud");
        Debug.Assert(shader != null, "Shader not found.");
        _cloudMaterial = CoreUtils.CreateEngineMaterial(shader);
        Debug.Assert(_cloudMaterial != null, "Material not create");
        renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; // renderPassEvent
        // Set any material properties based on our pass settings. 

        #region ShadowMappingRelated

        // _depthCamera = GameObject.Find("depthCamera").GetComponent<Camera>();
        // _biasMatrix = Matrix4x4.identity;
        // _biasMatrix[0, 0] = 0.5f;
        // _biasMatrix[1, 1] = 0.5f;
        // _biasMatrix[2, 2] = 0.5f;
        // _biasMatrix[0, 3] = 0.5f;
        // _biasMatrix[1, 3] = 0.5f;
        // _biasMatrix[2, 3] = 0.5f;

        #endregion
    }

    // Gets called by the renderer before executing the pass.
    // Can be used to configure render targets and their clearing state.
    // Can be user to create temporary render target textures.
    // If this method is not overriden, the render pass will render to the active camera render target.
    public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    {
        // Grab the color buffer from the renderer camera color target.
        _source = renderingData.cameraData.renderer.cameraColorTarget;
        var stack = VolumeManager.instance.stack;
        // Setup postprocess volume
        _cloudsVolume = stack.GetComponent<HoudiniCloudsVolume>();
        Debug.Assert(_cloudsVolume != null, "Clouds Volume is Null");
        Debug.Assert(_cloudsVolume.IsActive(), "Clouds Volume is Not Active");
        Debug.Assert(SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.RHalf), "RenderTextureFormat RHalf not support");
        Debug.Assert(SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.Default), "RenderTextureFormat Default not support");
        // Create temporary render texture
        ref var cameraData = ref renderingData.cameraData;
        var width = cameraData.camera.scaledPixelWidth;
        var height = cameraData.camera.scaledPixelHeight;
        var widthDepth = width / _cloudsVolume.passDepthDownSample.value;
        var heightDepth = height / _cloudsVolume.passDepthDownSample.value;
        var widthOne = width / _cloudsVolume.passOneDownSample.value;
        var heightOne = height / _cloudsVolume.passOneDownSample.value;
        var widthTwo = width / _cloudsVolume.passTwoDownSample.value;
        var heightTwo = height / _cloudsVolume.passTwoDownSample.value;
        var widthThree = width / _cloudsVolume.passThreeDownSample.value;
        var heightThree = height / _cloudsVolume.passThreeDownSample.value;
        downSampleDepthId = Shader.PropertyToID("_DownSampleDepth");
        downSampleOnceId = Shader.PropertyToID("_DownSampleOnce");
        downSampleTwiceId = Shader.PropertyToID("_DownSampleTwice");
        downSampleThirdId = Shader.PropertyToID("_DownSampleThird");
        shadowmapId = Shader.PropertyToID("_Shadowmap");
        colorCloudId = Shader.PropertyToID("_ColorCloud");
        finalColorId = Shader.PropertyToID("_OutputImage");
        cmd.GetTemporaryRT(downSampleDepthId, widthDepth, heightDepth, 0, FilterMode.Bilinear, RenderTextureFormat.RHalf, RenderTextureReadWrite.Linear);
        cmd.GetTemporaryRT(downSampleOnceId, widthOne, heightOne, 0, FilterMode.Bilinear, RenderTextureFormat.RHalf, RenderTextureReadWrite.Linear);
        cmd.GetTemporaryRT(downSampleTwiceId, widthTwo, heightTwo, 0, FilterMode.Bilinear, RenderTextureFormat.RHalf, RenderTextureReadWrite.Linear);
        cmd.GetTemporaryRT(downSampleThirdId, widthThree, heightThree, 0, FilterMode.Bilinear, RenderTextureFormat.RHalf, RenderTextureReadWrite.Linear);
        cmd.GetTemporaryRT(shadowmapId, widthThree, heightThree, 0, FilterMode.Bilinear, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
        cmd.GetTemporaryRT(colorCloudId, widthThree, heightThree, 0, FilterMode.Bilinear, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
        cmd.GetTemporaryRT(finalColorId, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
    }

    #endregion

    // Called when the camera has finished rendering.
    // Here we release/cleanup any allocated resources that were created by this pass.
    // Gets called for all cameras in a camera stack.
    public override void OnCameraCleanup(CommandBuffer cmd)
    {
        // Since we created a temporary render texture in OnCameraSetup, we need to release the memory here to avoid a leak.
        if (cmd == null) throw new ArgumentNullException("Cmd is null");
        cmd.ReleaseTemporaryRT(downSampleDepthId);
        cmd.ReleaseTemporaryRT(downSampleOnceId);
        cmd.ReleaseTemporaryRT(downSampleTwiceId);
        cmd.ReleaseTemporaryRT(downSampleThirdId);
        cmd.ReleaseTemporaryRT(shadowmapId);
        cmd.ReleaseTemporaryRT(colorCloudId);
        cmd.ReleaseTemporaryRT(finalColorId);
    }

    // The actual execution of the pass. This is where custom rendering occurs.
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (!renderingData.cameraData.postProcessEnabled) return ; // must be here, otherwise UI error
        var cmd = CommandBufferPool.Get(KRenderTag);
        using (new ProfilingScope(cmd, new ProfilingSampler(KRenderTag))) Render(cmd, renderingData.cameraData); // Debug USE
        context.ExecuteCommandBuffer(cmd);
        cmd.Clear();
        CommandBufferPool.Release(cmd);
    }

    private void Render(CommandBuffer cmd, CameraData cameraData)
    {
        ParameterLogic(cameraData);

        #region 代码生成RT并操作Blit

        //首先进行深度降采样
        cmd.Blit(_source, downSampleDepthId, _cloudMaterial, 0);
        //然后进行第一次降采样，降采样的结果作为后一个降采样的输入
        cmd.Blit(_source, downSampleOnceId, _cloudMaterial, 1);
        //进行第二次降采样，降采样的结果作为后一个降采样的输入
        cmd.Blit(_source, downSampleTwiceId, _cloudMaterial, 2);
        //进行第三次降采样，结果作为最后上颜色的输入
        cmd.Blit(_source, downSampleThirdId, _cloudMaterial, 3);
        // 阴影贴图降采样
        cmd.Blit(_source, shadowmapId, _cloudMaterial, 4);
        //渲染云的颜色
        cmd.Blit(_source, colorCloudId, _cloudMaterial, 5);
        //执行背景颜色和云的混合
        cmd.Blit(_source, finalColorId, _cloudMaterial, 6);
        // copy result to scene, source and dest cannot be the same
        cmd.Blit(finalColorId, _source);
        // cmd.Blit(_cloudsVolume.shadowRt.value, _source, _cloudMaterial, 4);  // shadowmap debug

        #endregion
    }


    private void ParameterLogic(CameraData cameraData)
    {
        #region ShadowMappingRelated

        // Matrix4x4 depthProjectionMatrix = _depthCamera.projectionMatrix;
        // Matrix4x4 depthViewMatrix = _depthCamera.worldToCameraMatrix;
        // Matrix4x4 depthViewProj = depthProjectionMatrix * depthViewMatrix; // WS->VS->HS
        // Matrix4x4 depthViewProjBias = _biasMatrix * depthViewProj; // HS->? 
        // _cloudMaterial.SetMatrix(ShadowCamVpId, depthViewProjBias);
        // _cloudMaterial.SetMatrix(ShadowCamVId, depthViewMatrix);
        // _cloudMaterial.SetTexture(ShadowMapRTId, _depthCamera.targetTexture);
        // _cloudMaterial.SetFloat(LookupOffsetId, _cloudsVolume.lookupOffset.value);

        #endregion

        // Debug
        _cloudMaterial.SetInt(SkyModeId, _cloudsVolume.skyMode.value ? 1 : 0);
        _cloudMaterial.SetInt(DebugModeId, _cloudsVolume.debugMode.value);
        _cloudMaterial.SetInt(DebugIterationId, _cloudsVolume.debugIteration.value ? 1 : 0);
        // 主摄像机相关
        _cloudMaterial.SetMatrix(CamFrustumId, CamFrustum(cameraData));
        _cloudMaterial.SetMatrix(CamToWorldId, cameraData.camera.cameraToWorldMatrix); //可以将右手坐标系下的相机空间坐标正确转换为左手坐标系下的世界空间坐标
        // 云的形状相关
        _cloudMaterial.SetFloat(CloudScaleId, _cloudsVolume.cloudScale.value);
        _cloudMaterial.SetVector(CornerInId, new Vector4(0, 0, 0, 0));
        _cloudMaterial.SetVector(CornerOutId,
            new Vector3(_cloudsVolume.index.value.width, _cloudsVolume.index.value.height, _cloudsVolume.index.value.depth) * _cloudsVolume.highResScale.value * _cloudsVolume.cloudScale.value);
        _cloudMaterial.SetFloat(AccuracyId, _cloudsVolume.accuracy.value);
        _cloudMaterial.SetTexture(IndexId, _cloudsVolume.index.value);
        _cloudMaterial.SetTexture(DataId, _cloudsVolume.data.value);
        _cloudMaterial.SetTexture(NoiseId, _cloudsVolume.noise.value);
        _cloudMaterial.SetFloat(HighResScaleId, _cloudsVolume.highResScale.value);

        _cloudMaterial.SetFloat(DataTexelId, 5.0f / _cloudsVolume.data.value.height);
        _cloudMaterial.SetVector(ClipPanelId, _cloudsVolume.clipPlane.value);
        _cloudMaterial.SetVector(MovementId, new Vector4(_cloudsVolume.sinSpeed.value, _cloudsVolume.sinStrength.value, _cloudsVolume.noiseSpeed.value, _cloudsVolume.noiseStrength.value));
        _cloudMaterial.SetVector(FbmId, new Vector4(_cloudsVolume.fbm.value.x, _cloudsVolume.fbm.value.y, _cloudsVolume.fbm.value.z, _cloudsVolume.fbmFreq.value));
        // 其他参数与Debug
        _cloudMaterial.SetVector(ColorAdjustId, _cloudsVolume.colorAdjust.value);
        _cloudMaterial.SetVector(SkyColorId, _cloudsVolume.skyColor.value * _cloudsVolume.skyColorStrength.value);
        _cloudMaterial.SetVector(CloudColorId, _cloudsVolume.cloudColor.value * _cloudsVolume.cloudColorStrength.value);
        _cloudMaterial.SetVector(ShadowParmId, new Vector4(_cloudsVolume.shadowStrength.value, 0, 0, 0));
        // 云的散射
        _cloudMaterial.SetVector(PhaseParamId,
            new Vector4(_cloudsVolume.forwardScattering.value, _cloudsVolume.backScattering.value, _cloudsVolume.baseBrightness.value, _cloudsVolume.phaseFactor.value));
    }

    private static Matrix4x4 CamFrustum(CameraData cam)
    {
        Matrix4x4 frustum = Matrix4x4.identity;
        float fov = Mathf.Tan((cam.camera.fieldOfView * 0.5f) * Mathf.Deg2Rad); //半视锥角的tan

        Vector3 goUp = Vector3.up * fov;
        Vector3 goRight = Vector3.right * fov * cam.camera.aspect;

        Vector3 topLeft = (-Vector3.forward - goRight + goUp);
        Vector3 topRight = (-Vector3.forward + goRight + goUp);
        Vector3 btmRight = (-Vector3.forward + goRight - goUp);
        Vector3 btmLeft = (-Vector3.forward - goRight - goUp);

        frustum.SetRow(0, topLeft);
        frustum.SetRow(1, topRight);
        frustum.SetRow(2, btmRight);
        frustum.SetRow(3, btmLeft);
        return frustum;
    }
}